package com.fowo.api.mp.service.impl;

import com.alibaba.fastjson.JSON;
import com.fowo.api.common.util.HexUtils;
import com.fowo.api.common.util.ListUtils;
import com.fowo.api.mp.config.OfficialAccountProperties;
import com.fowo.api.mp.model.*;
import com.fowo.api.mp.service.OfficialAccountService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.util.ByteUtils;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;

import javax.annotation.Resource;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;

@Slf4j
@Service
@EnableConfigurationProperties(OfficialAccountProperties.class)
public class OfficialAccountServiceImpl implements OfficialAccountService {

    public static final String ServiceAccessTokenCacheKey = "OAAccessToken";
    public static final String ServiceJsApiTicketKey = "OAJsApiTicket";

    @Resource
    private OfficialAccountProperties officialAccountProperties;
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private RestTemplate restTemplate;


    @Override
    public String getAccessToken(boolean forceUpdate) {
        if (!forceUpdate) {
            String accessToken = (String) redisTemplate.opsForValue().get(ServiceAccessTokenCacheKey);
            if (accessToken != null) {
                return accessToken;
            }
        }

        String url = String.format(
                "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=%s&secret=%s",
                officialAccountProperties.getAppId(),
                officialAccountProperties.getAppSecret()
        );
        String resultString = restTemplate.getForObject(url, String.class);
        ServerAccessTokenResult result = JSON.parseObject(resultString, ServerAccessTokenResult.class);

        if (result != null && StringUtils.hasText(result.getAccessToken())) {
            redisTemplate.opsForValue().set(
                    ServiceAccessTokenCacheKey,
                    result.getAccessToken(),
                    Duration.ofSeconds(result.getExpiresIn())
            );
            return result.getAccessToken();
        }
        else if (result != null && StringUtils.hasText(result.getErrMsg())) {
            log.error("无法获取公众号接口调用凭据：{}", result.getErrMsg());
        }
        return null;
    }

    private String sha1(String data) {
        return HexUtils.toHexString(DigestUtils.sha1(data));
    }

    @Override
    public String checkSignature(CheckSignatureRequest request) throws Exception {
        List<String> list = new ArrayList<>();
        list.add(officialAccountProperties.getToken());
        list.add(request.getTimestamp());
        list.add(request.getNonce());
        list.sort(String::compareTo);
        String tmpStr = ListUtils.join("", list);
        String signToken = sha1(tmpStr);
        if (signToken.equals(request.getSignature())) {
            return request.getEchostr();
        }
        return null;
    }

    @Override
    public WxResult sendTemplateMessage(WxTemplateMessage message) throws Exception {
        String accessToken = getAccessToken();
        if(!StringUtils.hasText(accessToken)) {
            throw new Exception("未能获取接口调用凭据");
        }
        String url = String.format(
                "https://api.weixin.qq.com/cgi-bin/message/template/send?access_token=%s",
                accessToken
        );
        String wxResultString = restTemplate.postForObject(url, message, String.class);
        WxResult wxResult =JSON.parseObject(wxResultString, WxResult.class);
        wxResult.throwIfErrCode();
        return wxResult;
    }

    @Override
    public String getAuthorizeUrl(String redirectUrl, String state, boolean userInfoScope) {
        String scope = userInfoScope ? "snsapi_userinfo" : "snsapi_base";
        return String.format(
                "https://open.weixin.qq.com/connect/oauth2/authorize?appid=%s&redirect_uri=%s&response_type=code&scope=%s&state=%s#wechat_redirect",
                officialAccountProperties.getAppId(),
                URLEncoder.encode(redirectUrl, StandardCharsets.UTF_8),
                scope,
                state
        );
    }

    @Override
    public OaAccessToken getAccessTokenByCode(String code) throws Exception {
        String url = String.format(
                "https://api.weixin.qq.com/sns/oauth2/access_token?appid=%s&secret=%s&code=%s&grant_type=authorization_code",
                officialAccountProperties.getAppId(),
                officialAccountProperties.getAppSecret(),
                code
        );
        final String token = restTemplate.getForObject(url, String.class);
        OaAccessToken oaAccessToken = JSON.parseObject(token, OaAccessToken.class);
        oaAccessToken.throwIfErrCode();
        return oaAccessToken;
    }

    @Override
    public OaAccessToken getAccessTokenByRefresh(String refreshToken) throws Exception {
        String url = String.format(
                "https://api.weixin.qq.com/sns/oauth2/refresh_token?appid=%s&grant_type=refresh_token&refresh_token=%s",
                officialAccountProperties.getAppId(),
                refreshToken
        );
        final String token = restTemplate.getForObject(url, String.class);
        OaAccessToken oaAccessToken = JSON.parseObject(token, OaAccessToken.class);
        oaAccessToken.throwIfErrCode();
        return oaAccessToken;
    }

    @Override
    public WxUserInfo getUserInfoByAccessToken(String accessToken, String openid) throws Exception {
        String url = String.format(
                "https://api.weixin.qq.com/sns/userinfo?access_token=%s&openid=%s&lang=zh_CN",
                accessToken,
                openid
        );
        final String token = restTemplate.getForObject(url, String.class);
        WxUserInfo wxUserInfo = JSON.parseObject(token, WxUserInfo.class);
        wxUserInfo.throwIfErrCode();
        return wxUserInfo;
    }

    @Override
    public WxUserInfo getUserInfoByCode(String code) throws Exception {
        OaAccessToken accessToken = getAccessTokenByCode(code);
        if (!StringUtils.hasText(accessToken.getAccessToken())) {
            WxUserInfo wxUserInfo = new WxUserInfo();
            wxUserInfo.setErrCode(accessToken.getErrCode());
            wxUserInfo.setErrMsg(accessToken.getErrMsg());
            return wxUserInfo;
        }
        return getUserInfoByAccessToken(accessToken.getAccessToken(), accessToken.getOpenid());
    }

    public String getJsTicket() throws Exception {
        String ticket = (String)redisTemplate.opsForValue().get(ServiceJsApiTicketKey);
        if(StringUtils.hasText(ticket)) {
            return ticket;
        }

        String accessToken = getAccessToken();
        if (!StringUtils.hasText(accessToken)) {
            throw new Exception("未能获取接口调用凭据");
        }
        String url = String.format(
                "https://api.weixin.qq.com/cgi-bin/ticket/getticket?access_token=%s&type=jsapi",
                accessToken
        );
        final String jsTicketResultString = restTemplate.getForObject(url, String.class);
        JsTicketResult jsTicketResult = JSON.parseObject(jsTicketResultString, JsTicketResult.class);

        jsTicketResult.throwIfErrCode();
        ticket = jsTicketResult.getTicket();
        redisTemplate.opsForValue().set(ServiceJsApiTicketKey, ticket, Duration.ofSeconds(jsTicketResult.getExpiresIn()));
        return ticket;
    }
    
    @Override
    public JsApiConfigResult getJsApiConfig(String url) throws Exception {
        String jsapi_ticket = getJsTicket();
        String noncestr = RandomStringUtils.randomAlphabetic(16);
        String timestamp = String.valueOf(System.currentTimeMillis() / 1000);

        String tmpStr = String.format(
                "jsapi_ticket=%s&noncestr=%s&timestamp=%s&url=%s",
                jsapi_ticket,
                noncestr,
                timestamp,
                url
        );
        String signature = sha1(tmpStr);

        JsApiConfigResult r = new JsApiConfigResult();
        r.setAppId(officialAccountProperties.getAppId());
        r.setTimestamp(timestamp);
        r.setNonceStr(noncestr);
        r.setSignature(signature);

        return r;
    }
}
